﻿#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
using namespace std;

//4.多态的原理
//4.1虚函数表

//这里常考一道笔试题：sizeof(Base)是多少？
//class Base {
//public:
//	virtual void Func1() { cout << "Func1()" << endl; }
//private:
//	int _b = 1;
//};
//
//int main() {
//	Base b;
//	cout << sizeof(Base) << endl;//8
//	//通过观察测试我们发现b对象是8bytes，除了_b成员，还多一个__vfptr放在对象的前面
//	// (注意有些平台可能会放到对象的最后面，这个跟平台有关)，
//	//对象中的这个指针我们叫做虚函数表指针(v代表virtual，f代表function)。
//	//一个含有虚函数的类中都至少都有一个虚函数表指针，
//	//因为虚函数的地址要被放到虚函数表中，虚函数表也简称虚表。那么派生类中这个表放了些什么呢？
//	return 0;
//}


//针对上面的代码我们做出以下改造
//1.我们增加一个派生类Derive去继承Base
//2.Derive中重写Func1
//3.Base再增加一个虚函数Func2和一个普通函数Func3
//class Base {
//public:
//	virtual void Func1() { cout << "Base::Func1()" << endl; }
//	virtual void Func2() { cout << "Base::Func2()" << endl; }
//	void Func3() { cout << "Base::Func3()" << endl; }
//private:
//	int _b = 1;
//};
//
//class Derive : public Base {
//public:
//	virtual void Func1() { cout << "Derive::Func1()" << endl; }
//	//如果子类不重写虚函数，父子类的虚表指针指向不同的虚表，但虚表里的虚函数地址相同
//	//同一个类的不同对象虚表指针指向同一个虚表
//private:
//	int _d = 2;
//};
//int main() {
//	Base b;
//	Derive d;
//	return 0;
//}
//1.派生类对象d中包含一个虚表指针。d对象的内存结构由两部分组成：
//一部分是从父类继承下来的成员（其中包含继承自父类的虚表指针），另一部分是派生类自己的成员。
//2.基类b对象和派生类d对象虚表是不一样的，这里我们发现Func1完成了重写，
//所以d的虚表中存的是重写的Derive::Func1，所以虚函数的重写也叫作覆盖，
//覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法，覆盖是原理层的叫法。
//3.另外Func2继承下来后是虚函数，所以放进了虚表，Func3也继承下来了，
//但是不是虚函数，所以不会放进虚表。
//4.虚函数表本质是一个存虚函数指针的指针数组，一般情况这个数组最后面放了一个nullptr。
//5.总结一下派生类的虚表生成：
//  a.先将基类中的虚表内容拷贝一份到派生类虚表中
//  b.如果派生类重写了基类中某个虚函数，用派生类自己的虚函数覆盖虚表中基类的虚函数
//  c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
//6.虚函数存在哪的？
//答：对象中存虚表指针，虚表指针指向虚表；虚表中存虚函数地址。
//虚函数和普通函数一样，都是存在代码段的。

//虚表存在哪的？
//class Base {
//public:
//	virtual void Func1() { cout << "Base::Func1()" << endl; }
//	virtual void Func2() { cout << "Base::Func2()" << endl; }
//private:
//	int a;
//};
//void func() { cout << "func()" << endl; }
//int main() {
//	Base b1;
//	static int a = 0;
//	int b = 0;
//	int* p1 = new int;
//	const char* p2 = "hello world";
//	printf("静态区：%p\n", &a);
//	printf("栈：%p\n", &b);
//	printf("堆：%p\n", &p1);
//	printf("代码段：%p\n", &p2);
//	printf("虚表：%p\n", (int*)&b1);//vs中虚表指针通常存在前4个字节
//	printf("虚函数：%p\n", &(Base::Func1));//成员函数名要加&才能取到地址
//	printf("普通函数：%p\n", func);
//	//从打印的地址来看，虚表的地址和代码段靠的最近
//	//虚函数地址和普通函数地址靠的最近
//	//所以虚表存在代码段
//	return 0;
//}



//4.2多态的原理
//class Person {
//public:
//	virtual void BuyTicket() { cout << "买票-全价" << endl; }
//	virtual void func() {}
//private:
//	int a = 0;
//};
//
//class Student : public Person {
//public:
//	virtual void BuyTicket() { cout << "买票-半价" << endl; }
//private:
//	int b = 1;
//};
//
//void Func(Person& p) { p.BuyTicket(); }
//int main() {
//	Person p;
//	Student s;
//
//	Func(p);
//	Func(s);
//	return 0;
//}
//为什么只有当子类赋值给父类的指针/引用才能构成多态？

//1.父类指针/引用指向子类对象时
//a.指针或引用实际指向的是子类对象内存中的父类部分
//（因为子类对象的内存布局是父类成员在前，子类新增成员在后）。
//b.虚表指针（vptr）仍然是子类的虚表指针
//（因为对象本身是子类对象，虚表指针在构造时就被初始化为子类的虚表）。
//c.调用虚函数时，通过这个子类的虚表指针找到子类重写的虚函数（动态绑定）。

//2.对象赋值（切片）时
//a.子类对象赋值给父类对象，会发生对象切片（Object Slicing），
//即仅复制父类部分的成员（子类新增成员被丢弃）。
//b.虚表指针也会被覆盖为父类的虚表指针
//（因为赋值后，新对象是一个独立的父类对象，不再和子类有关联）。
//c.因此，通过这个父类对象调用虚函数时，只能调用父类的实现（多态失效）。

//为什么是父类虚表指针覆盖子类？
//当发生 Base b = d; （d 是子类对象）时：
//1.赋值操作的静态类型是 Base
//C++ 编译器只认为b是一个Base对象，因此只会拷贝d中属于Base的部分（包括Base的成员变量和虚表指针）。
//2.虚表指针的初始化规则
//a.对象的虚表指针是在构造函数中初始化的。
//b.Base b = d; 实际上调用的是Base的拷贝构造函数（或拷贝赋值运算符），
//而Base的拷贝构造函数会将虚表指针设为Base的虚表（因为b是一个新构造的Base对象，与Derived无关）。
//c.子类的虚表指针不会被复制到b中，因为Base的拷贝操作根本不“知道”子类的存在。
